// 
// Entify project
//  
// Author:
//       Lauri Taimila <lauri@taimila.com>
// 
// Copyright (c) 2010 Lauri Taimila
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using System.Text;
using System.Collections.Generic;

using Mono.Data.Sqlite;

using Taimila.Entify;

namespace Taimila.Entify.Sqlite
{
	/// <summary>
	/// Contains extension methods for all types used by EntityRequest.
	/// This includes EntityRequest itself as all the filters that can
	/// be used with EntityRequest.
	/// 
	/// These extension methods generate SQL query from EntityRequest
	/// and also populate command with SqliteParameters used by generated
	/// query.
	/// </summary>
	internal static class SqliteEntityRequestExtensions
	{
		/// <summary>
		/// Get SqliteCommand from EntityRequest. Command is ready to be exectuted
		/// after connection has been set for it.
		/// </summary>
		/// <param name="entityRequest">
		/// Extended object. <see cref="EntityRequest<T>"/>
		/// </param>
		/// <returns>
		/// SqliteCommand that contains SQL query and required parameters for it.  <see cref="SqliteCommand"/>
		/// </returns>
		public static SqliteCommand GetCommand<T>(this EntityRequest<T> entityRequest)
		{
			SqliteCommand command = new SqliteCommand();
			
			StringBuilder query = new StringBuilder();
			
			query.AppendLine("SELECT *");
			query.AppendLine("FROM [" + typeof(T).FullName + "]");
			
			// Add WHERE clause if data filters exists
			if(entityRequest.DataFilter != null)
			{
				query.AppendLine("WHERE " + SqliteEntityRequestExtensions.GetSql(entityRequest.DataFilter, ref command));
			}
			
			// Add ORDER BY clause if sort filter exists
			if(entityRequest.SortFilter != null)
			{
				query.AppendLine(entityRequest.SortFilter.GetSql(ref command));
			}
			
			// Add LIMIT clause if paging filter exists
			if(entityRequest.PagingFilter != null)
			{
				query.AppendLine(entityRequest.PagingFilter.GetSql(ref command));
			}
			
			command.CommandText = query.ToString();
			
			return command;
		}
		
		/// <summary>
		/// Get SQL from ValueFilter object.
		/// </summary>
		/// <param name="valueFilter">
		/// Value filter which SQL is returned. <see cref="ValueFilter"/>
		/// </param>
		/// <param name="command">
		/// SqliteCommand that is created by extended EntityRequest object. <see cref="SqliteCommand"/>
		/// </param>
		/// <returns>
		/// SQL string for value filtering <see cref="System.String"/>
		/// </returns>
		private static string GetSql(this ValueFilter valueFilter, ref SqliteCommand command)
		{			
			string parameter = SqliteEntityRequestExtensions.AddParameter(valueFilter.Value, ref command);	
			return valueFilter.TargetProperty + SqliteEntityRequestExtensions.MapFilterRule(valueFilter.FilterRule) + parameter;
		}
		
		/// <summary>
		/// Get SQL from ValueSetFilter object.
		/// </summary>
		/// <param name="command">
		/// SqliteCommand that is created by extended EntityRequest object.
		/// </param>
		private static string GetSql(this ValueSetFilter valueSetFilter, ref SqliteCommand command)
		{
			List<string> parameters = new List<string>();
			
			foreach(var value in valueSetFilter.Values)
			{
				parameters.Add(SqliteEntityRequestExtensions.AddParameter(value, ref command));
			}
			
			string not = "";
			
			if(valueSetFilter.Rule == ValueSetRule.Exlcude)
			{
				not = " NOT";
			}
			
			return valueSetFilter.TargetProperty + not + " IN (" + string.Join(", ", parameters.ToArray()) + ")";
		}
		
		/// <summary>
		/// Get SQL from RangeFilter object.
		/// </summary>
		/// <param name="command">
		/// SqliteCommand that is created by extended EntityRequest object.
		/// </param>
		private static string GetSql(this RangeFilter rangeFilter, ref SqliteCommand command)
		{
			string parameter1 = SqliteEntityRequestExtensions.AddParameter(rangeFilter.LowerLimit, ref command);
			string parameter2 = SqliteEntityRequestExtensions.AddParameter(rangeFilter.UpperLimit, ref command);
			
			return rangeFilter.TargetProperty + " BETWEEN " + parameter1 + " AND " + parameter2;
		}
		
		/// <summary>
		/// Get SQL from NotFilter object.
		/// </summary>
		/// <param name="command">
		/// SqliteCommand that is created by extended EntityRequest object.
		/// </param>
		private static string GetSql(this NotFilter notFilter, ref SqliteCommand command)
		{
			return "NOT " + SqliteEntityRequestExtensions.GetSql(notFilter.Filter, ref command);	
		}
		
		/// <summary>
		/// Get SQL from SortFilter object.
		/// </summary>
		/// <param name="command">
		/// SqliteCommand that is created by extended EntityRequest object.
		/// </param>
		private static string GetSql(this SortFilter sortFilter, ref SqliteCommand command)
		{
			List<string> parameters = new List<string>();
			
			foreach(var property in sortFilter.Properties)
			{
				parameters.Add(SqliteEntityRequestExtensions.AddParameter(property, ref command));
			}
			
			string direction = "ASC";
			
			if(sortFilter.Direction == SortDirection.Descending)
			{
				direction = "DESC";	
			}
			
			return "ORDER BY " + string.Join(", ", parameters.ToArray()) + " " + direction;
		}
		
		/// <summary>
		/// Get SQL from PagingFilter object.
		/// </summary>
		/// <param name="command">
		/// SqliteCommand that is created by extended EntityRequest object.
		/// </param>
		private static string GetSql(this PagingFilter pagingFilter, ref SqliteCommand command)
		{
			string parameter1 = SqliteEntityRequestExtensions.AddParameter(pagingFilter.Limit, ref command);
			string parameter2 = SqliteEntityRequestExtensions.AddParameter(pagingFilter.Offset, ref command);
			                       
			return "LIMIT " + parameter1 + " OFFSET " + parameter2;	
		}
		
		/// <summary>
		/// Get SQL from CompositeFilter object.
		/// </summary>
		/// <param name="command">
		/// SqliteCommand that is created by extended EntityRequest object.
		/// </param>
		private static string GetSql(this CompositeFilter compositeFilter, ref SqliteCommand command)
		{
			string compositeOperator = " AND ";
			List<string> innerSQLs = new List<string>();
			
			if(compositeFilter.Operator == CompositeOperator.Or)
			{
				compositeOperator = " OR ";	
			}
			
			foreach(var filter in compositeFilter.Filters)
			{
				innerSQLs.Add("(" + SqliteEntityRequestExtensions.GetSql(filter, ref command) + ")");	
			}
			
			return " (" + string.Join(compositeOperator, innerSQLs.ToArray()) + ") ";
		}
		
		/// <summary>
		/// Get SQL from MatchFilter object.
		/// </summary>
		/// <param name="command">
		/// SqliteCommand that is created by extended EntityRequest object.
		/// </param>
		private static string GetSql(this MatchFilter matchFilter, ref SqliteCommand command)
		{
			if(matchFilter.IsCaseSensitive)
			{
				return matchFilter.targetProperty + " GLOB " + SqliteEntityRequestExtensions.AddParameter(matchFilter.MatchString, ref command);
			}
			else
			{
				//TODO: Add support for non case sensitive search. LOWER() works only for ASCII characters and can't be used.
				return matchFilter.targetProperty + " GLOB " + SqliteEntityRequestExtensions.AddParameter(matchFilter.MatchString, ref command);
			}
		}
		
		/// <summary>
		/// Helper method for getting SQL from any IDataFilter. This method
		/// determines filter type and calls it's own GetSql.
		/// </summary>
		/// <param name="filter">
		/// Filter which SQL is returned. <see cref="IDataFilter"/>
		/// </param>
		/// <param name="command">
		/// SqliteCommand that is created by extended EntityRequest object.
		/// </param>
		/// <returns>
		/// Filter's SQL. <see cref="System.String"/>
		/// </returns>
		private static string GetSql(IDataFilter filter, ref SqliteCommand command)
		{
			//TODO: This method needs to be updated when new Filter class is presented. Could this method be implemented with reflection instead?
			
			if(filter is ValueFilter)
			{
				return (filter as ValueFilter).GetSql(ref command);
			}
			else if(filter is ValueSetFilter)
			{
				return (filter as ValueSetFilter).GetSql(ref command);
			}
			else if(filter is RangeFilter)
			{
				return (filter as RangeFilter).GetSql(ref command);
			}
			else if(filter is MatchFilter)
			{
				return (filter as MatchFilter).GetSql(ref command);
			}
			else if(filter is NotFilter)
			{
				return (filter as NotFilter).GetSql(ref command);
			}
			else if(filter is CompositeFilter)
			{
				return (filter as CompositeFilter).GetSql(ref command);
			}
			
			throw new ArgumentException("Unknown filter type '" + filter.GetType().Name + "'");
		}
		
		/// <summary>
		/// 
		/// </summary>
		/// <param name="rule">
		/// FilterRule enumeration. <see cref="FilterRule"/>
		/// </param>
		/// <returns>
		/// FilterRule as SQLite compatible string. <see cref="System.String"/>
		/// </returns>
		private static string MapFilterRule(FilterRule rule)
		{
			switch(rule)
			{
				case FilterRule.Equal:
					return " = ";
				
				case FilterRule.Unequal:
					return " <> ";
					
				case FilterRule.GreaterOrEqualThan:
					return " >= ";
					
				case FilterRule.GreaterThan:
					return " > ";
					
				case FilterRule.LessOrEqualThan:
					return " <= ";
					
				case FilterRule.LessThan:
					return " < ";
			}
			
			throw new ArgumentException("Unkown FilterRule: " + rule.ToString());
		}
		
		/// <summary>
		/// Add parameter to command and return parameter name for SQL query.
		/// </summary>
		/// <param name="value">
		/// Value to be added as parameter. <see cref="System.Object"/>
		/// </param>
		/// <param name="command">
		/// Command where parameter is added. <see cref="SqliteCommand"/>
		/// </param>
		/// <returns>
		/// Name of the parameter. <see cref="System.String"/>
		/// </returns>
		private static string AddParameter(object value, ref SqliteCommand command)
		{
			string parameterName = "@entity_request_param_";
			string uniqueName;
			int counter = 1;
			
			do
			{
				uniqueName = parameterName + counter;
				counter++;
			}
			while(command.Parameters.Contains(uniqueName));
			
			command.Parameters.AddWithValue(uniqueName, SqliteTypeMapper.GetValueForSqliteParameter(value));
			
			return uniqueName;
		}
	}
}
